from playwright.sync_api import Page
import yaml
from utils.generate_logs_util import get_log


class Base:
    """
    Playwright自动化测试基础类，封装常用的页面操作方法。
    """

    def __init__(self, page, yaml_path, page_node=None):
        """
        初始化基类

        参数:
            page: Playwright Page实例
            yaml_path: yaml配置文件路径
            page_node: 初始页面节点名称（可选）
        """
        self.page = page
        self.yaml_path = yaml_path
        self.logger = get_log(__name__)

        # 加载YAML中所有节点的定位信息
        self.all_locators = self.load_all_locators()

        # 初始化当前节点（如果指定）
        self.current_node = None
        if page_node:
            self.switch_node(page_node)  # 调用切换方法初始化节点

    def load_all_locators(self):
        """加载YAML文件中所有节点的定位信息"""
        try:
            with open(self.yaml_path, 'r', encoding='utf-8') as f:
                locators = yaml.safe_load(f) or {}  # 处理空文件
            self.logger.info(f"成功加载YAML配置：{self.yaml_path}")
            return locators
        except Exception as e:
            self.logger.error(f"加载YAML配置失败：{str(e)}")
            raise  # 抛出异常，避免后续操作出错

    def switch_node(self, node_name):
        """
        切换当前操作的YAML节点
        参数:
            node_name: 要切换的节点名称（需在YAML中存在）
        """
        if node_name not in self.all_locators:
            error_msg = f"YAML中不存在节点：{node_name}（可用节点：{list(self.all_locators.keys())}）"
            self.logger.error(error_msg)
            raise KeyError(error_msg)

        self.current_node = node_name
        self.locators = self.all_locators[node_name]  # 当前节点的定位信息
        self.logger.info(f"已切换到节点：{node_name}")

    def open_url(self, url: str):
        """
        打开指定URL
        :param url: 网址
        """
        self.page.goto(url)

    def get_locator(self, loc_key):
        """
        根据当前节点的定位信息获取Playwright Locator对象
        参数:
            loc_key: 元素在当前节点下的键名（如"username_input"）
        """
        # 校验当前节点是否已设置
        if not self.current_node:
            error_msg = "未指定当前节点，请先调用switch_node()切换节点"
            self.logger.error(error_msg)
            raise ValueError(error_msg)

        # 获取元素定位信息
        locator_info = self.locators.get(loc_key)
        if not locator_info:
            error_msg = f"节点{self.current_node}中不存在元素：{loc_key}"
            self.logger.error(error_msg)
            raise KeyError(error_msg)

        # 解析定位方式和定位值
        by = locator_info["by"].strip().lower()  # 去除空格并转为小写
        value = locator_info["value"]
        timeout = locator_info.get("timeout", 5000)  # 默认为5秒（毫秒）

        # 根据定位方式获取locator
        if by == "xpath":
            locator = self.page.locator(f"xpath={value}")
        elif by == "css":
            locator = self.page.locator(value)
        elif by == "id":
            locator = self.page.locator(f"id={value}")
        elif by == "name":
            locator = self.page.locator(f"name={value}")
        elif by == "class_name":
            locator = self.page.locator(f".{value}")
        elif by == "tag_name":
            locator = self.page.locator(f"{value}")
        elif by == "link_text":
            locator = self.page.locator(f"text={value}")
        elif by == "partial_link_text":
            locator = self.page.locator(f"text*={value}")
        else:
            raise ValueError(f"不支持的定位方式: {by}")

        return locator

    def find_element(self, loc_key):
        """
        查找并等待元素可见
        :param loc_key: 元素定位键名
        """
        locator = self.get_locator(loc_key)
        # 等待元素可见
        locator.wait_for(state="visible")
        return locator

    def click(self, loc_key):
        """
        点击指定元素
        :param loc_key: 元素定位键名
        """
        self.find_element(loc_key).click()

    def fill(self, loc_key, info):
        """
        向指定元素输入内容
        :param loc_key: 元素定位键名
        :param info: 输入内容
        """
        self.find_element(loc_key).fill(info)

    def clear(self, loc_key):
        """
        清空输入框内容
        :param loc_key: 元素定位键名
        """
        locator = self.find_element(loc_key)
        locator.fill("")

    def text_content(self, loc_key):
        """
        获取元素文本内容
        :param loc_key: 元素定位键名
        :return: 文本内容
        """
        return self.find_element(loc_key).text_content()

    # 多窗口切换
    def new_handles(self):
        """
        窗口切换
        """
        # 等待新页面出现
        with self.page.context.expect_page() as new_page_info:
            new_page = new_page_info.value
        new_page.wait_for_load_state()
        self.page = new_page
        self.logger.info("已切换到最新打开的页面")

    # # 进入frame框架
    # def frame(self, loc_key):
    #     """
    #     进入指定frame框架
    #     :param loc_key: frame定位键名
    #     """
    #     # frame_locator = self.get_locator(loc_key).frame_locator
    #     # self.page = frame_locator.content_frame()
    #     iframe = self.page.frame_locator(loc_key)
    #     return iframe

    def frame(self, loc_key):
        """
        进入指定frame框架（基于YAML配置的定位键）
        :param loc_key: frame在YAML中的定位键名
        :return: FrameLocator对象，用于操作frame内元素
        """
        # 复用已有的定位逻辑获取frame的定位信息
        frame_locator_info = self.locators.get(loc_key)
        if not frame_locator_info:
            error_msg = f"节点{self.current_node}中不存在frame定位：{loc_key}"
            self.logger.error(error_msg)
            raise KeyError(error_msg)

        # 解析frame的定位方式和值（支持XPath、CSS等）
        by = frame_locator_info["by"].strip().lower()
        value = frame_locator_info["value"].strip()

        # 根据定位方式构建frame定位器
        if by == "xpath":
            iframe = self.page.frame_locator(f"xpath={value}")
        elif by == "css":
            iframe = self.page.frame_locator(value)
        elif by == "id":
            iframe = self.page.frame_locator(f"id={value}")
        else:
            # 对于其他定位方式，尝试直接作为选择器处理
            iframe = self.page.frame_locator(value)

        self.logger.info(f"已获取frame定位器：{loc_key}")
        return iframe

    # 退出frame框架
    def frame_exit(self):
        """
        退出frame，回到默认页面
        """
        self.page = self.page.main_frame.page

    # 滚动到任意像素
    def scrollto_x(self, x):
        """
        页面滚动到指定像素位置
        :param x: 像素值
        """
        self.page.evaluate(f'window.scrollTo(0, {x})')

    # 滚动到底部
    def scrollto_height(self):
        """
        页面滚动到底部
        """
        self.page.evaluate('window.scrollTo(0, document.body.scrollHeight)')

    # 弹窗 accept
    def accept(self):
        """
        接受弹窗
        """
        self.page.on("dialog", lambda dialog: dialog.accept())

    # 弹窗 dismiss
    def dismiss(self):
        """
        取消弹窗
        """
        self.page.on("dialog", lambda dialog: dialog.dismiss())

    # 弹窗 send_keys accept
    def send_accept(self, info):
        """
        向弹窗输入内容并接受
        :param info: 输入内容
        """
        self.page.on("dialog", lambda dialog: dialog.accept(info))

    # 弹窗 send_keys dismiss
    def send_dismiss(self, info):
        """
        向弹窗输入内容并取消
        :param info: 输入内容
        """
        self.page.on("dialog", lambda dialog: dialog.dismiss())

    # 下拉框选择
    def select_index(self, loc_key, index):
        """
        下拉框按索引选择
        :param loc_key: 下拉框定位键名
        :param index: 索引值
        """
        locator = self.find_element(loc_key)
        locator.select_option(index=index)

    def select_value(self, loc_key, value):
        """
        下拉框按value选择
        :param loc_key: 下拉框定位键名
        :param value: value值
        """
        locator = self.find_element(loc_key)
        locator.select_option(value=value)

    def select_text(self, loc_key, text):
        """
        下拉框按文本选择
        :param loc_key: 下拉框定位键名
        :param text: 显示文本
        """
        locator = self.find_element(loc_key)
        locator.select_option(label=text)

    # 鼠标操作
    def a_double(self, loc_key):
        """
        鼠标双击指定元素
        :param loc_key: 元素定位键名
        """
        self.find_element(loc_key).dblclick()

    def a_contex(self, loc_key):
        """
        鼠标右击指定元素
        :param loc_key: 元素定位键名
        """
        self.find_element(loc_key).click(button="right")

    def a_move(self, loc_key):
        """
        鼠标悬停到指定元素
        :param loc_key: 元素定位键名
        """
        self.find_element(loc_key).hover()

    def a_tuo(self, source_key, target_key):
        """
        鼠标拖拽元素到目标位置
        :param source_key: 源元素定位键名
        :param target_key: 目标元素定位键名
        """
        source = self.find_element(source_key)
        target = self.find_element(target_key)
        source.drag_to(target)

    def a_three(self, loc_key):
        """
        鼠标三击指定元素
        :param loc_key: 元素定位键名
        """
        locator = self.find_element(loc_key)
        locator.click(click_count=3)

    def a_hold(self, loc_key):
        """
        鼠标按住指定元素不释放
        :param loc_key: 元素定位键名
        """
        locator = self.find_element(loc_key)
        locator.click(button="left", hold=True)

    # 键盘事件
    def keys_enter(self, loc_key):
        """
        向元素发送回车键
        :param loc_key: 元素定位键名
        """
        self.find_element(loc_key).press("Enter")

    def keys_ctrl_c(self, loc_key):
        """
        向元素发送Ctrl+C组合键
        :param loc_key: 元素定位键名
        """
        self.find_element(loc_key).press("Control+C")

    def keys_ctrl_a(self, loc_key):
        """
        向元素发送Ctrl+A组合键
        :param loc_key: 元素定位键名
        """
        self.find_element(loc_key).press("Control+A")

    def keys_ctrl_x(self, loc_key):
        """
        向元素发送Ctrl+X组合键
        :param loc_key: 元素定位键名
        """
        self.find_element(loc_key).press("Control+X")

    def keys_ctrl_v(self, loc_key):
        """
        向元素发送Ctrl+V组合键
        :param loc_key: 元素定位键名
        """
        self.find_element(loc_key).press("Control+V")

    def keys_tab(self, loc_key):
        """
        向元素发送Tab键
        :param loc_key: 元素定位键名
        """
        self.find_element(loc_key).press("Tab")

    def keys_back(self, loc_key):
        """
        向元素发送退格键
        :param loc_key: 元素定位键名
        """
        self.find_element(loc_key).press("Backspace")

    def keys_del(self, loc_key):
        """
        向元素发送Delete键
        :param loc_key: 元素定位键名
        """
        self.find_element(loc_key).press("Delete")

    def keys_shift(self, loc_key):
        """
        向元素发送Shift键
        :param loc_key: 元素定位键名
        """
        self.find_element(loc_key).press("Shift")

    def keys_left(self, loc_key):
        """
        向元素发送左方向键
        :param loc_key: 元素定位键名
        """
        self.find_element(loc_key).press("ArrowLeft")

    def keys_right(self, loc_key):
        """
        向元素发送右方向键
        :param loc_key: 元素定位键名
        """
        self.find_element(loc_key).press("ArrowRight")

    def keys_top(self, loc_key):
        """
        向元素发送上方向键
        :param loc_key: 元素定位键名
        """
        self.find_element(loc_key).press("ArrowUp")

    def keys_bot(self, loc_key):
        """
        向元素发送下方向键
        :param loc_key: 元素定位键名
        """
        self.find_element(loc_key).press("ArrowDown")